home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2001 December
/
pcwk12201b.iso
/
Wersje pelne i specjalne
/
Winamp 2.77 i 3.0beta
/
wasabi-sdk_beta1.exe
/
studio
/
common
/
treewnd.cpp
< prev
next >
Wrap
C/C++ Source or Header
|
2001-10-08
|
38KB
|
1,600 lines
/*
Nullsoft WASABI Source File License
Copyright 1999-2001 Nullsoft, Inc.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Brennan Underwood
brennan@nullsoft.com
*/
#include "treewnd.h"
#include "canvas.h"
#include "stack.h"
#include "scrollbar.h"
#include "sepwnd.h"
#include "skinclr.h"
#include "notifmsg.h"
#include "../studio/api.h"
#define DEF_TEXT_SIZE 14
#define CHILD_INDENT itemHeight
#define X_SHIFT 2
#define Y_SHIFT 2
#define DRAG_THRESHOLD 4
#define TIMER_EDIT_DELAY 1000
#define TIMER_EDIT_ID 1249
///////////////////////////////////////////////////////////////////////////////
// TreeWnd
///////////////////////////////////////////////////////////////////////////////
static SkinColor textcolor("studio.tree.text");
static SkinColor drophilitecolor("studio.tree.hiliteddrop");
static SkinColor selectedcolor("studio.tree.selected");
int CompareTreeItem::compareItem(TreeItem *p1, TreeItem *p2) {
return p1->getTree()->compareItem(p1, p2);
}
TreeWnd::TreeWnd() {
tabClosed = NULL;
tabOpen = NULL;
linkTopBottom = NULL;
linkTopRight = NULL;
linkTopRightBottom = NULL;
linkTabTopBottom = NULL;
linkTabTopRight = NULL;
linkTabTopRightBottom = NULL;
curSelected = NULL;
mousedown_item = NULL;
hitItem = NULL;
draggedItem = NULL;
tipitem = NULL;
edited = NULL;
editwnd = NULL;
metrics_ok = FALSE;
setSorted(TRUE);
setFontSize(DEF_TEXT_SIZE);
redraw = TRUE;
prevbdownitem = NULL;
autoedit=0;
autocollapse=1;
}
TreeWnd::~TreeWnd() {
// delete all root items
deleteAllItems();
// why, francis? why? --BU
// coz eventho i know it works, i can't convince myself that deleting without checking isn't bad... somehow :) -FG
if (tabOpen) delete tabOpen;
if (tabClosed) delete tabClosed;
if (linkTopBottom) delete linkTopBottom;
if (linkTopRight) delete linkTopRight;
if (linkTopRightBottom) delete linkTopRightBottom;
if (linkTabTopBottom) delete linkTabTopBottom;
if (linkTabTopRight) delete linkTabTopRight;
if (linkTabTopRightBottom) delete linkTabTopRightBottom;
drawList.removeAll();
}
int TreeWnd::onInit() {
TREEWND_PARENT::onInit();
setBgBitmap("studio.tree.background");
setLineHeight(itemHeight);
tabClosed = new SkinBitmap("studio.tree.tab.closed");
tabOpen = new SkinBitmap("studio.tree.tab.open");
linkTopBottom = new SkinBitmap("studio.tree.link.top.bottom");
linkTopRight = new SkinBitmap("studio.tree.link.top.right");
linkTopRightBottom = new SkinBitmap("studio.tree.link.top.rightBottom");
linkTabTopBottom = new SkinBitmap("studio.tree.link.tab.top.bottom");
linkTabTopRight = new SkinBitmap("studio.tree.link.tab.top.right");
linkTabTopRightBottom = new SkinBitmap("studio.tree.link.tab.top.rightBottom");
return 1;
}
void TreeWnd::setRedraw(BOOL r) {
int old = redraw;
redraw = r;
if (!old && redraw)
invalidate();
}
int TreeWnd::onPaint(Canvas *canvas) {
PaintCanvas paintcanvas;
PaintBltCanvas paintbcanvas;
if (canvas == NULL) {
if (needDoubleBuffer()) {
if (!paintbcanvas.beginPaintNC(this)) return 0;
canvas = &paintbcanvas;
} else {
if (!paintcanvas.beginPaint(this)) return 0;
canvas = &paintcanvas;
}
}
TREEWND_PARENT::onPaint(canvas);
/* uncomment if you add columns or anything that should be not be drawn over by onPaint in which case you'll have to clip->subtract(your_region)
Region *clip = new Region();
canvas->getClipRgn(clip); */
RECT r;
getNonClientRect(&r);
int y = -getScrollY()+Y_SHIFT+r.top;
int x = -getScrollX()+X_SHIFT;
canvas->pushTextColor(textcolor);
canvas->setTextOpaque(FALSE);
canvas->pushTextSize(getFontSize());
firstItemVisible = NULL;
lastItemVisible = NULL;
ensureMetricsValid();
//drawSubItems(canvas, x, &y, items, r.top, r.bottom, 0);
drawItems(canvas);
canvas->popTextColor();
canvas->popTextSize();
canvas->selectClipRgn(NULL); // reset cliping region - NEEDED;
// delete clip; uncomment if necessary
return 1;
}
void TreeWnd::drawItems(Canvas *canvas) {
RECT r, c, ir;
Region *orig=NULL;
getClientRect(&r);
if (GetClipBox(canvas->getHDC(), &c) == NULLREGION) {
getClientRect(&c);
orig = new Region(&c);
} else
orig = new Region(canvas);
int first = ((c.top-r.top) + getScrollY() - Y_SHIFT) / itemHeight;
int last = ((c.bottom-r.top) + getScrollY() - Y_SHIFT) / itemHeight + 1;
POINT pt;
TreeItem *item;
BOOL hastab;
for (int i=first;i<=last;i++) {
if (i >= drawList.getNumItems()) break;
item = drawList[i];
if (!item) continue;
item->getCurRect(&ir);
pt.x = X_SHIFT+item->getIndent()*itemHeight - getScrollX();//ir.left;
pt.y = ir.top;
// if we need the +/- icon and any of the link lines, draw them
if (item->needTab()) {
// pt.x += itemHeight;
RECT _r={pt.x-itemHeight, pt.y, pt.x, pt.y+itemHeight};
(item->isCollapsed() ? tabClosed : tabOpen)->stretchToRectAlpha(canvas, &_r);
hastab=TRUE;
} else hastab = FALSE;
int indent = item->getIndent();
for (int j=0;j<indent;j++) {
RECT _r={pt.x-itemHeight*(j+1), pt.y, pt.x-itemHeight*j, pt.y+itemHeight};
int l = getLinkLine(item, j);
if (l == (LINK_RIGHT | LINK_TOP)) {
((hastab && j == 0) ? linkTabTopRight : linkTopRight)->stretchToRectAlpha(canvas, &_r);
}
if (l == (LINK_RIGHT | LINK_TOP | LINK_BOTTOM)) {
((hastab && j == 0) ? linkTabTopRightBottom : linkTopRightBottom)->stretchToRectAlpha(canvas, &_r);
}
if (l == (LINK_BOTTOM | LINK_TOP)) {
((hastab && j == 0) ? linkTabTopBottom : linkTopBottom)->stretchToRectAlpha(canvas, &_r);
}
}
item->customDraw(canvas, pt, itemHeight, (pt.x+getScrollX())-r.left-X_SHIFT, r);
}
if (orig) delete orig;
}
TreeItem *TreeWnd::hitTest(int x, int y) {
POINT pt={x,y};
return hitTest(pt);
}
TreeItem *TreeWnd::hitTest(POINT pt) {
RECT r, ir;
getClientRect(&r);
int first = (getScrollY() - Y_SHIFT) / itemHeight;
int last = ((r.bottom-r.top) + getScrollY() - Y_SHIFT) / itemHeight + 1;
TreeItem *item;
for (int i=first;i<=last;i++) {
if (i >= drawList.getNumItems()) break;
item = drawList.enumItem(i);
if (item)
{
item->getCurRect(&ir);
if (PtInRect(&ir, pt)) //TODO: implement in std.cpp
return item;
}
}
return NULL;
}
void TreeWnd::getMetrics(int *numItemsShown, int *mWidth) {
*mWidth=0;
*numItemsShown=0;
drawList.removeAll();
countSubItems(drawList, &items, X_SHIFT, numItemsShown, mWidth, 0);
}
void TreeWnd::countSubItems(PtrList<TreeItem> &drawlist, TreeItemList *_list, int indent, int *count, int *maxwidth, int z) {
int w;
TreeItemList &list = *_list;
for (int i=0;i<list.getNumItems();i++) {
TreeItem *nextitem = list[i];
w = nextitem->getItemWidth(itemHeight, indent-X_SHIFT);
if (indent+w > *maxwidth) *maxwidth = w+indent;
int j = indent-(nextitem->needTab() ? itemHeight : 0);
int k;
k = indent + w;
nextitem->setCurRect(j, Y_SHIFT+(*count * itemHeight), k, Y_SHIFT+((*count+1) * itemHeight), z);
(*count)++;
drawlist.addItem(nextitem);
if (nextitem->isExpanded())
countSubItems(drawlist, nextitem->subitems, indent+CHILD_INDENT, count, maxwidth, z+1);
}
}
void TreeWnd::timerCallback(int c) {
switch (c) {
case TIMER_EDIT_ID:
prevbdownitem = NULL;
killTimer(TIMER_EDIT_ID);
break;
default:
TREEWND_PARENT::timerCallback(c);
}
}
int TreeWnd::onLeftButtonDown(int x, int y) {
if (edited)
cancelEditLabel(1);
POINT pt={x,y};
TreeItem *item = hitTest(pt);
if (item) {
mousedown_item = item;
mousedown_anchor.x = pt.x;
mousedown_anchor.y = pt.y;
mousedown_dragdone = FALSE;
// only do expand/collapse if was already selected
setCurItem(item, autocollapse?(curSelected == item):0, FALSE);
beginCapture();
}
return 1;
}
int TreeWnd::onLeftButtonUp(int x, int y) {
if (getCapture())
endCapture();
TREEWND_PARENT::onLeftButtonUp(x, y);
POINT pt={x,y};
TreeItem *item = hitTest(pt);
if (autoedit && item == mousedown_item && item == prevbdownitem)
setCurItem(item, FALSE, TRUE);
else
if (autoedit) {
prevbdownitem = getCurItem();
setTimer(TIMER_EDIT_ID, TIMER_EDIT_DELAY);
}
mousedown_item = NULL;
return 1;
}
int TreeWnd::onRightButtonUp(int x, int y){
TREEWND_PARENT::onRightButtonUp(x, y);
POINT pos={x,y};
TreeItem *ti = hitTest(pos);
if (ti != NULL) {
selectItem(ti);
return ti->onContextMenu(pos.x, pos.y);
} else {
return onContextMenu(x, y);
}
return 0;
}
int TreeWnd::onMouseMove(int x, int y) {
TREEWND_PARENT::onMouseMove(x, y);
POINT pt={x,y};
if (mousedown_item) {
if (!mousedown_dragdone && (ABS(pt.x - mousedown_anchor.x) > DRAG_THRESHOLD || ABS(pt.y - mousedown_anchor.y) > DRAG_THRESHOLD)) {
mousedown_dragdone = TRUE;
if (getCapture())
endCapture();
onBeginDrag(mousedown_item);
}
} else {
TreeItem *item = hitTest(pt);
if (item) {
if (tipitem != item) {
tipitem = item;
RECT r;
RECT c;
getClientRect(&c);
item->getCurRect(&r);
if (r.right > c.right || r.bottom > c.bottom || r.top < c.top || r.left < c.left)
setLiveTip(item->getLabel());
}
} else {
setTip(getTip());
tipitem = NULL;
}
}
return 1;
}
int TreeWnd::onLeftButtonDblClk(int x, int y) {
TreeItem *item = hitTest(x, y);
if (item == NULL) return 0;
return item->onLeftDoubleClick();
}
int TreeWnd::onRightButtonDblClk(int x, int y) {
TreeItem *item = hitTest(x, y);
if (item == NULL) return 0;
return item->onRightDoubleClick();
}
void TreeWnd::setTip(const char *tip) {
defaultTip = tip;
setLiveTip(defaultTip);
}
void TreeWnd::setLiveTip(const char *tip) {
TREEWND_PARENT::setTip(tip);
}
const char *TreeWnd::getLiveTip() {
return TREEWND_PARENT::getTip();
}
const char *TreeWnd::getTip() {
return defaultTip;
}
int TreeWnd::onBeginDrag(TreeItem *treeitem) {
char title[WA_MAX_PATH];
// item calls addDragItem()
if (!treeitem->onBeginDrag(title)) return 0;
ASSERT(draggedItem == NULL);
draggedItem = treeitem;
if (*title != 0) setSuggestedDropTitle(title);
handleDrag();
return 1;
}
int TreeWnd::dragEnter(RootWnd *sourceWnd) {
// uh... we don't know yet, but we can accept drops in general
hitItem = NULL;
return 1;
}
int TreeWnd::dragOver(int x, int y, RootWnd *sourceWnd) {
POINT pos={x,y};
screenToClient(&pos);
TreeItem *prevItem;
prevItem = hitItem;
hitItem = hitTest(pos);
// no dropping on yourself! :)
if (hitItem == draggedItem) hitItem = NULL;
// unselect previous item
if (prevItem != hitItem && prevItem != NULL) {
unhiliteDropItem(prevItem);
repaint(); // commit invalidation of unhilited item so no trouble with scrolling
prevItem->dragLeave(sourceWnd);
}
RECT r;
getClientRect(&r);
if (pos.y < r.top + 16) {
if (getScrollY() >= 0) {
scrollToY(max(0, getScrollY()-itemHeight));
}
} else if (pos.y > r.bottom - 16) {
if (getScrollY() < getMaxScrollY()) {
scrollToY(min(getMaxScrollY(), getScrollY()+itemHeight));
}
}
if (hitItem != NULL) {
// hilight it
if (prevItem != hitItem) {
hiliteDropItem(hitItem);
repaint(); // commit invalidation of hilited so no trouble with scrolling
}
}
if (hitItem == NULL) return defaultDragOver(x, y, sourceWnd);
// ask the item if it can really accept such a drop
return hitItem->dragOver(sourceWnd);
}
int TreeWnd::dragLeave(RootWnd *sourceWnd) {
if (hitItem != NULL) {
unhiliteDropItem(hitItem);
hitItem->dragLeave(sourceWnd);
}
hitItem = NULL;
return 1;
}
int TreeWnd::dragDrop(RootWnd *sourceWnd, int x, int y) {
int res;
if (hitItem == NULL) return defaultDragDrop(sourceWnd, x, y);
// unhilite the dest
unhiliteDropItem(hitItem);
// the actual drop
res = hitItem->dragDrop(sourceWnd);
if (res) {
onItemRecvDrop(hitItem);
}
hitItem = NULL;
return res;
}
int TreeWnd::dragComplete(int success) {
int ret;
ASSERT(draggedItem != NULL);
ret = draggedItem->dragComplete(success);
draggedItem = NULL;
return ret;
}
void TreeWnd::hiliteDropItem(TreeItem *item) {
if (item)
item->setHilitedDrop(TRUE);
}
void TreeWnd::hiliteItem(TreeItem *item) {
if (item)
item->setHilited(TRUE);
}
void TreeWnd::selectItem(TreeItem *item) {
setCurItem(item, FALSE);
}
void TreeWnd::selectItemDeferred(TreeItem *item) {
postDeferredCallback(DC_SETITEM, (int)item);
}
void TreeWnd::delItemDeferred(TreeItem *item) {
postDeferredCallback(DC_DELITEM, (int)item);
}
void TreeWnd::unhiliteItem(TreeItem *item) {
if (item)
item->setHilited(FALSE);
}
void TreeWnd::unhiliteDropItem(TreeItem *item) {
if (item)
item->setHilitedDrop(FALSE);
}
void TreeWnd::setCurItem(TreeItem *item, BOOL expandCollapse, BOOL editifselected) {
if (curSelected && curSelected != item) {
curSelected->setSelected(FALSE);
}
if (item) {
curSelected = item;
item->setSelected(TRUE, expandCollapse, editifselected);
setSlidersPosition();
}
}
// Returns the current tree width in pixels
int TreeWnd::getContentsWidth() {
ensureMetricsValid();
return maxWidth;
}
// Returns the current tree height in pixels
int TreeWnd::getContentsHeight() {
ensureMetricsValid();
return maxHeight;
}
void TreeWnd::ensureMetricsValid() {
if (metrics_ok) return;
int n;
getMetrics(&n, &maxWidth);
maxWidth += X_SHIFT*2;
maxHeight = n*itemHeight+Y_SHIFT*2;
metrics_ok = TRUE;
setSlidersPosition();
}
// Gets notification from sliders
int TreeWnd::childNotify(RootWnd *child, int msg, int param1, int param2) {
switch (msg) {
case CHILD_EDIT_DATA_MODIFIED:
if (child == editwnd && editwnd != NULL) {
endEditLabel(editbuffer);
return 1;
}
break;
case CHILD_EDIT_CANCEL_PRESSED:
if (child == editwnd && editwnd != NULL) {
cancelEditLabel();
return 1;
}
break;
case UMSG_EDIT_UPDATE:
if (child == editwnd && editwnd != NULL) {
editUpdate();
return 1;
}
break;
}
return TREEWND_PARENT::childNotify(child, msg, param1, param2);
}
void TreeWnd::editUpdate() {
ASSERT(edited != NULL && editwnd != NULL);
if (!edited || !editwnd) return;
int w = editwnd->getTextLength()+16;
RECT i, r, e;
edited->getCurRect(&i);
getClientRect(&r);
editwnd->getClientRect(&e);
e.left += i.left;
e.right += i.left;
e.top += i.top;
e.bottom += i.top;
e.right = i.left+w;
e.right = min(r.right - X_SHIFT, e.right);
editwnd->resize(&e);
editwnd->invalidate();
}
TreeItem *TreeWnd::addTreeItem(TreeItem *item, TreeItem *par, int _sorted, int haschildtab) {
ASSERT(item != NULL);
ASSERTPR(item->getTree() == NULL, "can't transplant TreeItems");
item->setSorted(_sorted);
item->setChildTab(haschildtab && par != NULL);
item->linkTo(par);
item->setTree(this);
if (par == NULL)
items.addItem(item);
metrics_ok = FALSE;
if (redraw)
invalidate();
item->onTreeAdd();
return item;
}
int TreeWnd::removeTreeItem(TreeItem *item) {
ASSERT(item != NULL);
ASSERT(item->getTree() == this);
if (item->isSelected()) item->setSelected(FALSE);
if (curSelected == item) curSelected = NULL;
//CUT item->deleteSubitems();
TreeItem *par = item->getParent();
if (!par) { // is root item ?
ASSERT(items.haveItem(item));
items.removeItem(item);
} else {
if (!par->removeSubitem(item))
return 0;
}
metrics_ok = FALSE;
drawList.removeItem(item);
if (redraw)
invalidate();
item->setTree(NULL);
item->onTreeRemove();
return 1;
}
void TreeWnd::moveTreeItem(TreeItem *item, TreeItem *newparent) {
ASSERT(item != NULL);
ASSERTPR(item->getTree() == this, "can't move between trees (fucks up Freelist)");
removeTreeItem(item);
addTreeItem(item, newparent);
}
void TreeWnd::deleteAllItems() {
BOOL save_redraw = redraw;
setRedraw(FALSE);
TreeItem *item;
while ((item = enumRootItem(0)) != NULL)
delete item;
setRedraw(save_redraw);
}
void TreeWnd::setSorted(int dosort) {
items.setAutoSort(dosort);
}
BOOL TreeWnd::getSorted() {
return items.getAutoSort();
}
void TreeWnd::sortTreeItems() {
items.sort(TRUE);
metrics_ok = FALSE;
if (redraw)
invalidate();
}
TreeItem *TreeWnd::getSibling(TreeItem *item) {
for (int i=0;i<items.getNumItems();i++) {
if (items[i] == item) {
if (i == items.getNumItems()-1) return NULL;
return items[i+1];
}
}
return NULL;
}
void TreeWnd::setAutoCollapse(BOOL doautocollase) {
autocollapse=doautocollase;
}
int TreeWnd::onContextMenu(int x, int y) {
POINT pos={x,y};
screenToClient(&pos);
TreeItem *ti = hitTest(pos);
if (ti != NULL) {
selectItem(ti);
return ti->onContextMenu(x, y);
}
return 0;
}
int TreeWnd::onDeferredCallback(int param1, int param2) {
switch (param1) {
case DC_SETITEM:
setCurItem((TreeItem *)param2, FALSE);
return 1;
case DC_DELITEM:
delete (TreeItem *)param2;
return 1;
case DC_EXPAND:
expandItem((TreeItem *)param2);
return 1;
case DC_COLLAPSE:
collapseItem((TreeItem *)param2);
return 1;
}
return 0;
}
int TreeWnd::getNumRootItems() {
return items.getNumItems();
}
TreeItem *TreeWnd::enumRootItem(int which) {
return items[which];
}
void TreeWnd::invalidateMetrics() {
metrics_ok = FALSE;
}
int TreeWnd::getLinkLine(TreeItem *item, int level) {
ASSERT(item != NULL);
int l = 0;
int r = 0;
if (item->parent == NULL)
return 0;
TreeItem *cur=item;
while (cur->getParent() && l < level) {
cur = cur->getParent();
l++;
}
if (cur->getSibling()) r |= LINK_BOTTOM | LINK_TOP;
if (level == 0) r |= LINK_RIGHT;
if (level == 0 && cur->getParent()) r |= LINK_TOP;
return r;
}
int TreeWnd::onMouseWheelDown(int clicked, int lines) {
if (!clicked)
scrollToY(min(getMaxScrollY(), getScrollY()+itemHeight));
else
scrollToX(min(getMaxScrollX(), getScrollX()+itemHeight));
return 1;
}
int TreeWnd::onMouseWheelUp(int clicked, int lines) {
if (!clicked)
scrollToY(max(0, getScrollY()-itemHeight));
else
scrollToX(max(0, getScrollX()-itemHeight));
return 1;
}
int TreeWnd::expandItem(TreeItem *item) {
ASSERT(item != NULL);
return item->expand();
}
void TreeWnd::expandItemDeferred(TreeItem *item) {
postDeferredCallback(DC_EXPAND, (int)item);
}
int TreeWnd::collapseItem(TreeItem *item) {
ASSERT(item != NULL);
return item->collapse();
}
void TreeWnd::collapseItemDeferred(TreeItem *item) {
postDeferredCallback(DC_COLLAPSE, (int)item);
}
TreeItem *TreeWnd::getCurItem() {
return curSelected;
}
int TreeWnd::getItemRect(TreeItem *item, RECT *r) {
ASSERT(item != NULL);
return item->getCurRect(r);
}
void TreeWnd::editItemLabel(TreeItem *item) {
if (edited) {
edited->setEdition(FALSE);
edited->invalidate();
}
ASSERT(item != NULL);
if (item == NULL) return;
if (item->onBeginLabelEdit()) return;
item->setEdition(TRUE);
edited = item;
editwnd = new EditWnd();
editwnd->setModal(TRUE);
editwnd->setAutoSelect(TRUE);
editwnd->setStartHidden(TRUE);
editwnd->init(gethInstance(), gethWnd());
editwnd->setParent(this);
RECT r;
edited->getCurRect(&r);
RECT cr;
getClientRect(&cr);
r.right = cr.right;
if (r.bottom - r.top < 24) r.bottom = r.top + 24;
editwnd->resize(&r);
STRCPY(editbuffer, edited->getLabel());
editwnd->setBuffer(editbuffer, 255);
editUpdate();
editwnd->setVisible(TRUE);
}
void TreeWnd::endEditLabel(char *newlabel) {
editwnd = NULL; // editwnd self destructs
if (edited->onEndLabelEdit(newlabel))
edited->setLabel(newlabel);
edited->setEdition(FALSE);
edited->invalidate();
onLabelChange(edited);
edited = NULL;
invalidateMetrics();
setSlidersPosition();
}
void TreeWnd::cancelEditLabel(int destroyit) {
ASSERT(edited != NULL);
if (!edited) return;
if (destroyit)
delete editwnd;
editwnd = NULL; // editwnd self destructs (update> except if destroyit for cancelling from treewnd)
edited->setEdition(FALSE);
edited->invalidate();
edited = NULL;
}
void TreeWnd::setAutoEdit(int ae) {
autoedit = ae;
}
int TreeWnd::getAutoEdit() {
return autoedit;
}
TreeItem *TreeWnd::getByLabel(TreeItem *item, char *name) {
TreeItem *ti;
// handle root-level searching
if (item == NULL) {
int n = getNumRootItems();
for (int i = 0; i < n; i++) {
ti = enumRootItem(i);
if (STREQL(name, ti->getLabel())) return ti;
ti = getByLabel(ti, name);
if (ti) return ti;
}
return NULL;
}
// check the given item
if (STREQL(name, item->getLabel())) return item;
// depth first search
ti = item->getChild();
if (ti != NULL) {
ti = getByLabel(ti, name);
if (ti != NULL) return ti;
}
// recursively check siblings
ti = item->getSibling();
if (ti != NULL) ti = getByLabel(ti, name);
return ti;
}
int TreeWnd::onKillFocus() {
TREEWND_PARENT::onKillFocus();
mousedown_item=NULL;
/* if (edited)
cancelEditLabel();*/
return 1;
}
int TreeWnd::onChar(char c) {
int r = 0;
if (c == 27) {
if (edited)
cancelEditLabel(1);
}
if (curSelected != NULL && (r = curSelected->onChar(c)) != 0) return r;
char b = TOUPPER(c);
if (b >= 'A' && b <= 'Z') {
jumpToNext(b);
r = 1;
}
return r ? r : TREEWND_PARENT::onChar(c);
}
int TreeWnd::getNumVisibleChildItems(TreeItem *c) {
int nb=0;
for(int i=0;i<c->getNumChildren();i++) {
TreeItem *t=c->getNthChild(i);
if(t->hasSubItems() && t->isExpanded())
nb+=getNumVisibleChildItems(t);
nb++;
}
return nb;
}
int TreeWnd::getNumVisibleItems() {
int nb=0;
for(int i=0;i<items.getNumItems();i++) {
TreeItem *t=items.enumItem(i);
if(t->hasSubItems() && t->isExpanded())
nb+=getNumVisibleChildItems(t);
nb++;
}
return nb;
}
TreeItem *TreeWnd::enumVisibleChildItems(TreeItem *c, int n) {
int nb=0;
for(int i=0;i<c->getNumChildren();i++) {
TreeItem *t=c->getNthChild(i);
if(nb==n) return t;
if(t->hasSubItems() && t->isExpanded()) {
TreeItem *t2=enumVisibleChildItems(t, n-nb-1);
if(t2) return t2;
nb+=getNumVisibleChildItems(t);
}
nb++;
}
return NULL;
}
TreeItem *TreeWnd::enumVisibleItems(int n) {
int nb=0;
for(int i=0;i<items.getNumItems();i++) {
TreeItem *t=items.enumItem(i);
if(nb==n) return t;
if(t->hasSubItems() && t->isExpanded()) {
TreeItem *t2=enumVisibleChildItems(t, n-nb-1);
if(t2) return t2;
nb+=getNumVisibleChildItems(t);
}
nb++;
}
return NULL;
}
int TreeWnd::onKeyDown(int keycode) {
switch(keycode) {
case 113: {
TreeItem *item = getCurItem();
if (item)
item->editLabel();
return 1;
}
case VK_UP: {
TreeItem *t=getCurItem();
int l=getNumVisibleItems();
for(int i=0;i<l;i++)
if(enumVisibleItems(i)==t) {
if(i-1>=0) {
TreeItem *t2=enumVisibleItems(i-1);
if(t2) setCurItem(t2,FALSE,FALSE);
}
}
return 1;
}
case VK_DOWN: {
TreeItem *t=getCurItem();
int l=getNumVisibleItems();
for(int i=0;i<l;i++)
if(enumVisibleItems(i)==t) {
TreeItem *t2=enumVisibleItems(i+1);
if(t2) setCurItem(t2,FALSE,FALSE);
}
return 1;
}
case VK_PRIOR: {
TreeItem *t=getCurItem();
int l=getNumVisibleItems();
for(int i=0;i<l;i++)
if(enumVisibleItems(i)==t) {
int a=MAX(i-5,0);
TreeItem *t2=enumVisibleItems(a);
if(t2) setCurItem(t2,FALSE,FALSE);
}
return 1;
}
case VK_NEXT: {
TreeItem *t=getCurItem();
int l=getNumVisibleItems();
for(int i=0;i<l;i++)
if(enumVisibleItems(i)==t) {
int a=MIN(i+5,l-1);
TreeItem *t2=enumVisibleItems(a);
if(t2) setCurItem(t2,FALSE,FALSE);
}
return 1;
}
case VK_HOME: {
TreeItem *t=enumVisibleItems(0);
if(t) setCurItem(t,FALSE,FALSE);
return 1;
}
case VK_END: {
TreeItem *t=enumVisibleItems(getNumVisibleItems()-1);
if(t) setCurItem(t,FALSE,FALSE);
return 1;
}
case VK_LEFT: {
TreeItem *t=getCurItem();
if(t) t->collapse();
return 1;
}
case VK_RIGHT: {
TreeItem *t=getCurItem();
if(t) t->expand();
return 1;
}
}
return TREEWND_PARENT::onKeyDown(keycode);
}
void TreeWnd::jumpToNext(char c) {
firstFound=FALSE;
if (jumpToNextSubItems(&items, c)) return;
firstFound=TRUE;
jumpToNextSubItems(&items, c);
}
int TreeWnd::jumpToNextSubItems(TreeItemList *list, char c) {
for (int i=0;i<list->getNumItems();i++) {
TreeItem *nextitem = list->enumItem(i);
const char *l = nextitem->getLabel();
char b = l ? TOUPPER(*l) : 0;
if (b == c && firstFound) {
selectItem(nextitem);
nextitem->ensureVisible();
return 1;
}
if (nextitem->isSelected()) firstFound = TRUE;
if (nextitem->isExpanded())
if (jumpToNextSubItems(nextitem->subitems, c)) return 1;
}
return 0;
}
void TreeWnd::ensureItemVisible(TreeItem *item) {
ASSERT(item != NULL);
// walk the parent tree to make sure item is visible
for (TreeItem *cur = item->getParent(); cur; cur = cur->getParent()) {
if (cur->isCollapsed()) cur->expand();
}
RECT r;
RECT c;
item->getCurRect(&r);
getClientRect(&c);
if (r.top < c.top || r.bottom > c.bottom) {
if (r.top + (c.bottom - c.top) <= getContentsHeight())
scrollToY(r.top);
else {
scrollToY(getContentsHeight()-(c.bottom-c.top));
}
}
}
void TreeWnd::setHilitedColor(const char *colorname) {
// we have to store it in a String because SkinColor does not make a copy
hilitedColorName = colorname;
hilitedColor = hilitedColorName;
}
COLORREF TreeWnd::getHilitedColor() {
return hilitedColor;
}
void TreeWnd::freeResources() {
TREEWND_PARENT::freeResources();
if (tabClosed) delete tabClosed;
if (tabOpen) delete tabOpen;
if (linkTopBottom) delete linkTopBottom;
if (linkTopRight) delete linkTopRight;
if (linkTopRightBottom) delete linkTopRightBottom;
if (linkTabTopBottom) delete linkTabTopBottom;
if (linkTabTopRight) delete linkTabTopRight;
if (linkTabTopRightBottom) delete linkTabTopRightBottom;
tabClosed = NULL;
tabOpen = NULL;
linkTopBottom = NULL;
linkTopRight = NULL;
linkTopRightBottom = NULL;
linkTabTopBottom = NULL;
linkTabTopRight = NULL;
linkTabTopRightBottom = NULL;
}
void TreeWnd::reloadResources() {
TREEWND_PARENT::reloadResources();
tabClosed = new SkinBitmap("studio.tree.tab.closed");
tabOpen = new SkinBitmap("studio.tree.tab.open");
linkTopBottom = new SkinBitmap("studio.tree.link.top.bottom");
linkTopRight = new SkinBitmap("studio.tree.link.top.right");
linkTopRightBottom = new SkinBitmap("studio.tree.link.top.rightBottom");
linkTabTopBottom = new SkinBitmap("studio.tree.link.tab.top.bottom");
linkTabTopRight = new SkinBitmap("studio.tree.link.tab.top.right");
linkTabTopRightBottom = new SkinBitmap("studio.tree.link.tab.top.rightBottom");
}
int TreeWnd::compareItem(TreeItem *p1, TreeItem *p2) {
return STRCMP(p1->getLabel(), p2->getLabel());
}
int TreeWnd::setFontSize(int newsize) {
TREEWND_PARENT::setFontSize(newsize);
if (newsize >= 0) textsize = newsize;
DCCanvas *c = new DCCanvas();
HDC dc = GetDC(NULL); // Nonportable.
c->cloneDC(dc);
c->pushTextSize(getFontSize());
itemHeight = c->getTextHeight();
c->popTextSize();
delete c;
ReleaseDC(NULL, dc);
redraw = 1;
metrics_ok = 0;
invalidate();
return 1;
}
int TreeWnd::getFontSize() {
return textsize + api->metrics_getDelta();
}
////////////////////////////////////////////////////////////////////////////////////
// TreeItem
////////////////////////////////////////////////////////////////////////////////////
TreeItem::TreeItem() {
parent=NULL;
subitems = new TreeItemList();
MEMZERO(&curRect, sizeof(RECT));
childTab = TAB_AUTO;
tree = NULL;
expandStatus = STATUS_COLLAPSED;
icon = NULL;
_z = 0;
selected = FALSE;
hilitedDrop = FALSE;
hilited = FALSE;
being_edited = FALSE;
setSorted(TRUE);
}
TreeItem::~TreeItem() {
// the subitem will call parent tree which will remove item from our list
deleteSubitems();
// remove from parent tree
if (tree) tree->removeTreeItem(this);
delete subitems;
delete icon;
}
void TreeItem::deleteSubitems() {
while (subitems->getNumItems() > 0) {
delete subitems->enumItem(0);
}
}
void TreeItem::setSorted(int issorted) {
subitems->setAutoSort(issorted);
}
void TreeItem::setChildTab(int haschildtab) {
childTab = haschildtab;
}
void TreeItem::linkTo(TreeItem *par) {
parent = par;
if (par == NULL) return;
par->addSubItem(this);
}
void TreeItem::addSubItem(TreeItem *item) {
subitems->addItem(item);
}
int TreeItem::removeSubitem(TreeItem *item) {
if (subitems->searchItem(item) == -1) return 0;
subitems->removeItem(item);
if (tree->redraw)
tree->invalidate();
return 1;
}
TreeItem *TreeItem::getChild() {
return subitems->getFirst();
}
TreeItem *TreeItem::getChildSibling(TreeItem *item) { // locate item in children and return its sibling
for (int i=0;i<subitems->getNumItems();i++) {
if (subitems->enumItem(i) == item) {
if (i == subitems->getNumItems()-1) return NULL;
return subitems->enumItem(i+1);
}
}
return NULL;
}
TreeItem *TreeItem::getSibling() { // returns next item
if (!parent)
return tree->getSibling(this);
else
return parent->getChildSibling(this);
}
void TreeItem::setTree(TreeWnd *newtree) {
tree = newtree;
// recursively reset tree for children, if any
for (int i = 0; ; i++) {
TreeItem *item = getNthChild(i);
if (item == NULL) break;
item->setTree(tree);
}
}
void TreeItem::ensureVisible() {
if (tree) tree->ensureItemVisible(this);
}
const char *TreeItem::getLabel() {
return label;
}
void TreeItem::setLabel(const char *newlabel) {
label = newlabel;
if (newlabel) {
if (tree)
tree->invalidateMetrics();
invalidate();
}
}
int TreeItem::customDraw(Canvas *canvas, const POINT &pt, int txtHeight, int indentation, const RECT &clientRect) {
if (being_edited) return 0;
SkinBitmap *icon = getIcon();
int startPoint = pt.x - indentation;
if (isSelected() || isHilitedDrop()) {
RECT r;
r.left = pt.x;
r.top = pt.y;
r.right = r.left + canvas->getTextWidth(label)+2+(icon ? txtHeight : 0);
r.bottom = r.top + txtHeight;
canvas->fillRect(&r, isHilitedDrop() ? drophilitecolor : selectedcolor);
}
if (isHilited()) {
RECT r;
r.left = pt.x;
r.top = pt.y;
r.right = r.left + canvas->getTextWidth(label)+2+(icon ? txtHeight : 0);
r.bottom = r.top + txtHeight;
canvas->drawRect(&r, 1, tree->getHilitedColor());
}
POINT d=pt;
if (icon) {
RECT i;
i.left = pt.x+1;
i.right = i.left + txtHeight-2;
i.top = pt.y+1;
i.bottom = i.top + txtHeight-2;
icon->stretchToRectAlpha(canvas, &i);
d.x += txtHeight;
}
//CUT int x = pt.x;
canvas->textOut(d.x+1, d.y, label);
return canvas->getTextWidth(label)+2+(icon ? txtHeight : 0);
}
int TreeItem::getItemWidth(int txtHeight, int indentation) {
SkinBitmap *icon = getIcon();
if (!label) return (icon ? txtHeight : 0);
DCCanvas *c = new DCCanvas();
HDC dc = GetDC(NULL); //Nonportable.
c->cloneDC(dc);
c->pushTextSize(tree->getFontSize());
int width = c->getTextWidth(label)+2;
c->popTextSize();
delete c;
ReleaseDC(NULL, dc);
width += (icon ? txtHeight : 0);
return width;
}
int TreeItem::getNumChildren() {
return subitems->getNumItems();
}
TreeItem *TreeItem::getNthChild(int nth) {
if (nth >= subitems->getNumItems()) return NULL;
return subitems->enumItem(nth);
}
void TreeItem::setCurRect(int x1, int y1, int x2, int y2, int z) {
curRect.left = x1;
curRect.right = x2;
curRect.top = y1;
curRect.bottom = y2;
_z = z;
}
int TreeItem::getIndent() {
return _z;
}
BOOL TreeItem::hasSubItems() {
return subitems->getNumItems()>0;
}
BOOL TreeItem::needTab() {
return (childTab == TAB_YES || (childTab == TAB_AUTO && hasSubItems()));
}
BOOL TreeItem::isExpanded() {
if (!hasSubItems()) return FALSE;
return expandStatus == STATUS_EXPANDED;
}
BOOL TreeItem::isCollapsed() {
if (!hasSubItems()) return TRUE;
return expandStatus == STATUS_COLLAPSED;
}
int TreeItem::getCurRect(RECT *r) {
r->left = curRect.left-tree->getScrollX();
r->top = curRect.top-tree->getScrollY();
r->right = curRect.right-tree->getScrollX();
r->bottom = curRect.bottom-tree->getScrollY();
RECT c;
tree->getClientRect(&c);
r->top += c.top;
r->bottom += c.top;
r->left += c.left;
r->right += c.left;
return 1;
}
TreeWnd *TreeItem::getTree() const {
return tree;
}
void TreeItem::setSelected(int isSelected, BOOL expandCollapse, BOOL editifselected) {
int wasselected = selected;
selected = !!isSelected;
if (selected != wasselected) {
invalidate();
tree->repaint();
if (selected) {
onSelect();
ASSERT(tree != NULL);
tree->onItemSelected(this);
} else {
onDeselect();
ASSERT(tree != NULL);
tree->onItemDeselected(this);
}
} else {
if (selected && editifselected) {
editLabel();
}
}
if (expandCollapse) {
if (isCollapsed())
expand();
else
collapse();
}
}
void TreeItem::invalidate() {
if (tree) {
RECT r;
getCurRect(&r);
tree->invalidateRect(&r);
}
}
BOOL TreeItem::isSelected() {
return selected;
}
int TreeItem::collapse() {
if (!hasSubItems()) return 0;
int old = expandStatus;
if (expandStatus == STATUS_COLLAPSED)
return 0;
expandStatus = STATUS_COLLAPSED;
RECT c;
tree->getClientRect(&c);
RECT r;
getCurRect(&r);
r.bottom = c.bottom;
r.left = c.left;
r.right = c.right;
if (tree) {
tree->invalidateRect(&r);
tree->invalidateMetrics();
}
return old != expandStatus;
}
int TreeItem::expand() {
if (!hasSubItems()) return 0;
int old = expandStatus;
if (expandStatus == STATUS_EXPANDED)
return 0;
expandStatus = STATUS_EXPANDED;
RECT c;
tree->getClientRect(&c);
RECT r;
getCurRect(&r);
r.bottom = c.bottom;
r.left = c.left;
r.right = c.right;
if (tree) {
tree->invalidateRect(&r);
tree->invalidateMetrics();
}
return old != expandStatus;
}
int TreeItem::onContextMenu(int x, int y) {
return 0;
}
void TreeItem::setHilitedDrop(BOOL ishilitedDrop) {
int washilighted = hilitedDrop;
hilitedDrop = !!ishilitedDrop;
if (washilighted != hilitedDrop)
invalidate();
}
void TreeItem::setHilited(BOOL ishilited) {
int washilighted = hilited;
hilited = !!ishilited;
if (washilighted != hilited)
invalidate();
}
TreeItem *TreeItem::getParent() {
return parent;
}
BOOL TreeItem::isHilitedDrop() {
return hilitedDrop;
}
BOOL TreeItem::isHilited() {
return hilited;
}
void TreeItem::setIcon(SkinBitmap *newicon) {
if (icon) {
delete icon;
icon = NULL;
}
icon = newicon;
invalidate();
}
SkinBitmap *TreeItem::getIcon() {
return icon;
}
void TreeItem::sortItems() {
subitems->sort();
}
void TreeItem::editLabel() {
if (!tree) return;
tree->editItemLabel(this);
}
int TreeItem::onBeginLabelEdit() {
return 1; // disable editing by default
}
int TreeItem::onEndLabelEdit(const char *newlabel) {
return 1; // accept new label by default
}
void TreeItem::setEdition(BOOL isedited) {
being_edited = !!isedited;
invalidate();
}
BOOL TreeItem::getEdition() {
return being_edited;
}
BOOL TreeItem::isSorted() {
ASSERT(subitems == NULL);
return subitems->getAutoSort();
}